﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Aeon.Emulator.DebugSupport
{
    /// <summary>
    /// Contains a list of recently decoded instructions.
    /// </summary>
    [CLSCompliant(false)]
    public sealed class InstructionLog : IEnumerable<LoggedInstruction>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the InstructionLog class with a default size of 100.
        /// </summary>
        public InstructionLog()
            : this(100)
        {
        }
        /// <summary>
        /// Initializes a new instance of the InstructionLog class.
        /// </summary>
        /// <param name="logSize">Number of instructions to keep in the log.</param>
        public InstructionLog(int logSize)
        {
            this.instructions = new LoggedInstruction[logSize];
            for(int i = 0; i < this.instructions.Length; i++)
                this.instructions[i] = new LoggedInstruction();
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the last instruction written to the log.
        /// </summary>
        public LoggedInstruction LastInstruction
        {
            get
            {
                return lastInstruction;
            }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// Gets or sets the current segment override prefix.
        /// </summary>
        internal SegmentRegister SegmentOverridePrefix { get; set; }
        /// <summary>
        /// Gets or sets the current operand and address size prefix.
        /// </summary>
        internal int SizeOverridePrefix { get; set; }
        /// <summary>
        /// Gets or sets the current repeat prefix.
        /// </summary>
        internal RepeatPrefix RepeatPrefix { get; set; }
        #endregion

        #region Private Properties
        /// <summary>
        /// Gets the current prefixes in effect.
        /// </summary>
        private PrefixState PrefixState
        {
            get
            {
                PrefixState value;

                switch(this.SegmentOverridePrefix)
                {
                case SegmentRegister.CS:
                    value = PrefixState.CS;
                    break;

                case SegmentRegister.DS:
                    value = PrefixState.DS;
                    break;

                case SegmentRegister.ES:
                    value = PrefixState.ES;
                    break;

                case SegmentRegister.FS:
                    value = PrefixState.FS;
                    break;

                case SegmentRegister.GS:
                    value = PrefixState.GS;
                    break;

                case SegmentRegister.SS:
                    value = PrefixState.SS;
                    break;

                default:
                    value = PrefixState.None;
                    break;
                }

                if((this.SizeOverridePrefix & 1) != 0)
                    value |= PrefixState.OperandSize;

                if((this.SizeOverridePrefix & 2) != 0)
                    value |= PrefixState.AddressSize;

                switch(this.RepeatPrefix)
                {
                case RepeatPrefix.Repe:
                    value |= PrefixState.Repe;
                    break;

                case RepeatPrefix.Repne:
                    value |= PrefixState.Repne;
                    break;
                }

                return value;
            }
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// Logs an instruction, removing the oldest in the log if necessary.
        /// </summary>
        internal void Add(OpcodeInfo opcodeInfo, IntPtr rawCodes, Processor processor, uint ip)
        {
            var instruction = this.instructions[writeIndex];
            instruction.Assign(opcodeInfo, rawCodes, processor, ip);
            instruction.Prefixes = this.PrefixState;
            this.lastInstruction = instruction;

            writeIndex++;

            if(count < this.instructions.Length)
                count++;

            if(writeIndex >= this.instructions.Length)
                writeIndex = 0;

            this.SegmentOverridePrefix = SegmentRegister.Default;
            this.SizeOverridePrefix = 0;
        }
        #endregion

        #region Public Methods
        public IEnumerator<LoggedInstruction> GetEnumerator()
        {
            for(int i = 0; i < this.count; i++)
            {
                int index = (this.writeIndex + i) % this.count;
                yield return this.instructions[index];
            }
        }
        public IEnumerable<LoggedInstruction> At(ushort cs, uint eip)
        {
            return this.Where(i => i.CS == cs && i.EIP == eip);
        }
        public void Save(string fileName)
        {
            using(var writer = new StreamWriter(fileName))
            {
                foreach(var instruction in this)
                    writer.WriteLine(instruction);
            }
        }
        #endregion

        #region IEnumerable Members
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region Private Fields
        private LoggedInstruction[] instructions;
        private LoggedInstruction lastInstruction;
        private int writeIndex;
        private int count;
        #endregion
    }
}
